package com.rocky.pubimport.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.poi.excel.*;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.rocky.pubimport.common.ImportInfo;
import com.rocky.pubimport.common.PubExcelTemplate;
import com.rocky.pubimport.common.ResponseImportResult;
import com.rocky.pubimport.entity.PubImportData;
import com.rocky.pubimport.entity.PubImportRecord;
import com.rocky.pubimport.enums.PubImportStatus;
import com.rocky.pubimport.enums.PubInsertStatus;
import com.rocky.pubimport.mapper.PubImportRecordMapper;
import com.rocky.pubimport.service.IPubImportDataService;
import com.rocky.pubimport.service.IPubImportRecordProcessService;
import com.rocky.pubimport.service.IPubImportRecordService;
import com.rocky.pubimport.utils.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.DataFormat;
import org.apache.poi.ss.usermodel.Sheet;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.*;

/**
 * PubImportRecordProcessServiceImpl
 * @author rocky
 * @date 2022/4/13 17:01
 */
public abstract class PubImportRecordProcessServiceImpl<T> implements IPubImportRecordProcessService<T> {

    @Resource
    private PubImportRecordMapper pubImportRecordMapper;

    @Resource
    private IPubImportRecordService pubImportRecordService;

    @Resource
    private IPubImportDataService pubImportDataService;

    /** 允许上传的文件后缀名 */
    protected static final List<String> FILE_SUFFIX = Arrays.asList(".xls", ".xlsx");

    /**
     * 物理地址根路径
     */
    @Value("${file.rootPath}")
    private  String rootPath;

    /**
     * 获取业务模块
     * @return 业务模块
     */
    protected abstract String getModelCode();

    /**
     * 获取下载模板
     * @return PubExcelTemplate excel模板
     */
    protected abstract PubExcelTemplate getExcelTemplate();

    /**
     * 验证数据
     * @param data 数据
     */
    protected abstract void checkParams(T data);

    /**
     * 将中间表数据转换对应的数据对象
     * @param data 中间数据
     * @return T 对应的数据对象
     */
    protected abstract T readData(PubImportData data);

    /**
     * 保持对应的业务数据
     * @param data 业务数据
     */
    protected abstract void saveData(T data);

    /**
     * 回写保存成功业务对象的主键信息到导入记录里面
     * @param importData  导入记录
     * @param businessData 业务对象
     */
    protected abstract void writeExpand(PubImportData importData, T businessData);

    @Override
    public String getModel() {
        return getModelCode();
    }

    @Override
    public void downloadTemplate(HttpServletResponse response) {
        Map<String, Object> titleRow = new LinkedHashMap<>();
        PubExcelTemplate template = getExcelTemplate();
        for (String title : template.getTitleRow()) {
            titleRow.put(title, "");
        }
        ArrayList<Map<String, Object>> rows = CollUtil.newArrayList(titleRow);
        ExcelWriter writer = ExcelUtil.getWriter(true);
        // 设置单元格式为文本
        DataFormat format = writer.getWorkbook().createDataFormat();
        Sheet sheet = writer.getSheet();
        StyleSet styleSet = writer.getStyleSet();
        CellStyle cellStyle = styleSet.getCellStyle();
        cellStyle.setDataFormat(format.getFormat("@"));
        for (int i = 0; i < titleRow.size(); i++) {
            sheet.setDefaultColumnStyle(i, cellStyle);
        }
        writer.write(rows, true);
        response.setContentType("application/vnd.ms-excel;charset=utf-8");
        String fileName = template.getTemplateName() + ".xlsx";
        try {
            fileName = URLEncoder.encode(fileName,"UTF-8");
        } catch (UnsupportedEncodingException e) {
            fileName = "template.xlsx";
        }
        response.setHeader("Content-Disposition","attachment;filename=" + fileName);
        try {
            writer.flush(response.getOutputStream());
        } catch (IOException e) {
            throw new RuntimeException("下载模板失败");
        }
        // 关闭writer，释放内存
        writer.close();
    }

    @Override
    public ResponseImportResult importExcel(ImportInfo info) {
        if (info == null) {
            return ResponseImportResult.error("导入信息为空");
        }
        String currentModel = getModel();
        if (!StringUtils.equals(currentModel, info.getModel())) {
            return ResponseImportResult.error("业务模块不是" + currentModel);
        }
        if (info.getExcelFile() == null) {
            return ResponseImportResult.error("文件上传失败");
        }
        MultipartFile file = info.getExcelFile();
        String fileName = file.getOriginalFilename();
        String suffix = FileUtils.getFileExtension(fileName);
        if (!FILE_SUFFIX.contains(suffix)) {
            return ResponseImportResult.error("文件格式不正确");
        }
        List<PubImportData> importDataList = new ArrayList<>();
        // 存放标题
        List<String> titleRow = new ArrayList<>();
        try {
            ExcelReader reader = ExcelUtil.getReader(file.getInputStream());
            List<List<Object>> readList = reader.read(0);
            int readListSize = readList.size();
            // 导入数据行与标题栏不能大于1001
            if (readList.size() > 1001) {
                throw new RuntimeException("excel可导入数据不能超过1000条");
            }
            for (int i = 0; i < readListSize; i++) {
                List<Object> rowList = readList.get(i);
                if (rowList.isEmpty()) {
                    continue;
                }
                if (titleRow.isEmpty()) {
                    // 判断导入模板是否符合条件
                    for (Object obj : rowList) {
                        titleRow.add((String) obj);
                    }
                    List<String> setUpTitleRow = getExcelTemplate().getTitleRow();
                    int titleRowSize = titleRow.size();
                    if (titleRowSize != setUpTitleRow.size()) {
                        throw new RuntimeException("上传的文件不符合业务模块[" + info.getModel() + "的导入模板");
                    }
                    for (int j = 0; j < titleRowSize; j++) {
                        String fromSetUp = setUpTitleRow.get(j);
                        String fromImport = titleRow.get(j);
                        if (!StringUtils.equals(fromSetUp, fromImport)) {
                            throw new RuntimeException("上传的文件不符合业务模块[" + info.getModel() + "的导入模板");
                        }
                    }

                } else {
                    int size = rowList.size();
                    try {
                        Class dataClass = PubImportData.class;
                        Object dataObj = dataClass.newInstance();
                        for (int j = 0; j < size; j++) {
                            int idx = j + 1;
                            Field field = dataClass.getDeclaredField("value" + idx);
                            field.setAccessible(true);
                            field.set(dataObj, isNullOrEmpty( rowList.get(j)));
                        }
                        importDataList.add((PubImportData) dataObj);
                    } catch (Exception e) {
                        return ResponseImportResult.error("excel的单元格内容不是文本格式，请先设置单元格内容为文本格式");
                    }
                }
            }
        } catch (IOException e) {
            return ResponseImportResult.error("文件读取异常");
        } catch (RuntimeException e) {
            return ResponseImportResult.error(e.getMessage());
        }
        if (importDataList.isEmpty()) {
            return ResponseImportResult.error("excel可导入数据数量为0，请核对excel文件");
        }
        // 导入记录
        PubImportRecord importRecord = new PubImportRecord();
        importRecord.setBatchNo(DateFormatUtils.format(new Date(), "yyyyMMddHHmmssSSS"));
        importRecord.setModel(info.getModel());
        importRecord.setImportStatus(PubImportStatus.NO_STARTED.getCode());
        importRecord.setSuccessImportNum(0);
        importRecord.setErrorImportNum(0);
        importRecord.setSuccessInsertNum(0);
        importRecord.setErrorInsertNum(0);
        importRecord.setInsertStatus(PubInsertStatus.NO_STARTED.getCode());
        pubImportRecordService.save(importRecord);
        // 导入数据
        List<PubImportData> successImportDataList = new ArrayList<>();
        List<PubImportData> errorImportDataList = new ArrayList<>();
        for (PubImportData data : importDataList) {
            data.setRecordId(importRecord.getId());
            try {
                pubImportDataService.save(data);
                successImportDataList.add(data);
            } catch (Exception e) {
                errorImportDataList.add(data);
            }
        }
        int successNum = successImportDataList.size();
        int errorNum = errorImportDataList.size();
        importRecord.setSuccessImportNum(successNum);
        importRecord.setErrorImportNum(errorNum);
        if (errorNum == 0) {
            importRecord.setImportStatus(PubImportStatus.SUCCESS.getCode());
        } else if (successNum == 0) {
            importRecord.setImportStatus(PubImportStatus.ERROR.getCode());
        } else {
            importRecord.setImportStatus(PubImportStatus.PARTIAL_SUCCESS.getCode());
        }
        // 写回返回结果
        ResponseImportResult.ImportResult importResult = new ResponseImportResult.ImportResult();
        importResult.setImportStatus(importRecord.getImportStatus());
        importResult.setSuccessImportNum(importRecord.getSuccessImportNum());
        importResult.setErrorImportNum(importRecord.getErrorImportNum());
        return ResponseImportResult.ok(importResult);
    }

    @Override
    public void processData(PubImportRecord record) {
        // 记录处理信息
        pubImportRecordService.update(Wrappers.<PubImportRecord>lambdaUpdate()
                .set(PubImportRecord::getInsertStatus, PubInsertStatus.PROCESSING.getCode())
                .set(PubImportRecord::getInsertStartTime, new Date())
                .eq(PubImportRecord::getId, record.getId()));

        // 开始查询数据处理
        List<PubImportData> importDataList = pubImportDataService.list(new QueryWrapper<PubImportData>()
                .eq("`record_id`", record.getId()));
        List<PubImportData> successInsertDataList = new ArrayList<>();
        List<PubImportData> errorInsertDataList = new ArrayList<>();
        for (PubImportData data : importDataList) {
            T businessData = null;
            try {
                businessData = readData(data);
                checkParams(businessData);
            } catch(Exception e) {
                data.setInsertStatus(false);
                data.setInsertErrorMsg(e.getMessage());
                errorInsertDataList.add(data);
                pubImportDataService.updateById(data);
                continue;
            }
            try {
                saveData(businessData);
                writeExpand(data, businessData);
                data.setInsertStatus(true);
                successInsertDataList.add(data);
            } catch(Exception e) {
                data.setInsertStatus(false);
                data.setInsertErrorMsg(e.getMessage());
                errorInsertDataList.add(data);
            }
            pubImportDataService.updateById(data);
        }
        int successNum = successInsertDataList.size();
        int errorNum = errorInsertDataList.size();
        record.setSuccessInsertNum(successNum);
        record.setErrorInsertNum(errorNum);
        if (errorNum == 0) {
            record.setInsertStatus(PubInsertStatus.SUCCESS.getCode());
        } else if (successNum == 0) {
            record.setInsertStatus(PubInsertStatus.ERROR.getCode());
        } else {
            record.setInsertStatus(PubInsertStatus.PARTIAL_SUCCESS.getCode());
        }
        List<String> titleRow = getExcelTemplate().getTitleRow();
        // 生成对应的文件
        if (!successInsertDataList.isEmpty()) {
            String successFileName = UUID.randomUUID() + ".xlsx";
            record.setSuccessInsertFile(successFileName);
            String physicalPath = rootPath + successFileName;
            BigExcelWriter writer = ExcelUtil.getBigWriter(physicalPath);
            // 自定义标题别名
            int size = titleRow.size();
            for (int i = 0; i < size; i++) {
                int index = i + 1;
                writer.addHeaderAlias("value" + index, titleRow.get(i));
            }
            // 默认的，未添加alias的属性也会写出，如果想只写出加了别名的字段，可以调用此方法排除之
            writer.setOnlyAlias(true);
            // 一次性写出内容，使用默认样式
            writer.write(successInsertDataList);
            // 关闭writer，释放内存
            writer.close();
        }
        if (!errorInsertDataList.isEmpty()) {
            String errorFileName = UUID.randomUUID() + ".xlsx";
            record.setErrorInsertFile(errorFileName);
            String physicalPath = rootPath + errorFileName;
            BigExcelWriter writer = ExcelUtil.getBigWriter(physicalPath);
            // 自定义标题别名
            int size = titleRow.size();
            for (int i = 0; i < size; i++) {
                int index = i + 1;
                writer.addHeaderAlias("value" + index, titleRow.get(i));
            }
            writer.addHeaderAlias("insertErrorMsg", "生成失败原因");
            // 默认的，未添加alias的属性也会写出，如果想只写出加了别名的字段，可以调用此方法排除之
            writer.setOnlyAlias(true);
            // 一次性写出内容，使用默认样式
            writer.write(errorInsertDataList);
            // 关闭writer，释放内存
            writer.close();
        }
        record.setInsertEndTime(new Date());
        pubImportRecordMapper.updateById(record);
    }

    /**
     * 空处理
     * @param value 对象
     * @return String
     */
    private String isNullOrEmpty(Object value) {
        if (value == null) {
            return "";
        }
        return value.toString();
    }
}
